package br.edu.unifei.mestrado.unfold.mn;

import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import br.edu.unifei.mestrado.commons.graph.GraphWrapper;
import br.edu.unifei.mestrado.commons.graph.NodeWrapper;
import br.edu.unifei.mestrado.commons.graph.RelationshipWrapper;
import br.edu.unifei.mestrado.unfold.PartitionUnfold;

public class CoarseHelper {

	private Logger logger = LoggerFactory.getLogger(this.getClass());

	/**
	 * Usado para fazer commit a cada N nós novos
	 */
	private static final int COMMIT_INTERVAL = 1;

	private SetMatching mathcing = null;
	
	private PartitionUnfold partition;

	public CoarseHelper(PartitionUnfold partition) {
		this.partition = partition;
	}

	/**
	 * Faz a contração do grafo oldGraph salvando os novos vertices e arestas em newGraph.
	 * @param newLevel
	 * @param oldGraph
	 * @param newGraph
	 */
	public void coarseOneLevel(final int newLevel, GraphWrapper oldGraph, GraphWrapper newGraph) {
		logger.warn("Inicio do coarse NIVEL: " + newLevel);

		// instancia o matching novamente para não usar o coarsed vertex do nivel anterior
		mathcing = new SetMatching();

		long delta = System.currentTimeMillis();

		try {
			newGraph.beginTransaction();
			int i = coarseGraphWithNodes(oldGraph, newGraph);
			logger.info("Qtd vertices: " + oldGraph.getSizeNodes() + " qtd Coarsed seq: " + i);
			newGraph.endTransaction();
		} catch (Throwable e) {
			logger.error("Erro contraindo grafo nivel: " + newGraph.getLevel(), e);
		} finally {
			newGraph.finish();
		}
		setInsideOfForGraph(oldGraph);
		delta = System.currentTimeMillis() - delta;
		newGraph.printGraph();

		logger.warn("Qtd de vertices do nivel: " + newLevel + " : " + newGraph.getSizeNodes()
				+ " tempo gasto: " + delta + " ms");
	}

	private int coarseGraphWithNodes(GraphWrapper oldGraph, GraphWrapper newGraph) {
		
		Map<Integer, Iterable<NodeWrapper>> mapOneNode = new HashMap<Integer, Iterable<NodeWrapper>>();
		int i = 0;
		for (int setId = 0; setId < partition.getK(); setId++) {
			
			Iterable<NodeWrapper> nodes = partition.getNodesFrom(setId);
			if(nodes.iterator().hasNext()) {//só contrai os sets que possuem nodes.
				
				//TODO: verificar se dá para processar todas as arestas de uma só vez: acho que não, pois precisa agrupar os nodes do mesmo set
				Iterable<RelationshipWrapper> setEdgeCutList = partition.queryEdges(setId);
				mathcing.coarseSet(newGraph, setId, nodes, setEdgeCutList );
				i++;
			} else {
				mapOneNode.put(setId, nodes);
			}
		}
		// UTIL: Esse if é para não subir muito a memória e o GC não ocupar muito CPU
		if (i % COMMIT_INTERVAL == 0) {
			newGraph.endTransaction();
			newGraph.finish();
			logger.info("Qtd de nós contraídos: " + i);
			newGraph.beginTransaction();
			
		}
		mathcing.processNewArestas(newGraph);
		
//		//repassa os nodes que nao foram contraidos
//		
//		for (Integer setId : mapOneNode.keySet()) {
//			Iterable<NodeWrapper> nodes = mapOneNode.get(setId);
//			Iterable<RelationshipWrapper> setEdgeCutList = partition.queryEdges(setId);
//			mathcing.processRemainingVertices(newGraph, setId, nodes, setEdgeCutList );
//		}
		
		return newGraph.getSizeNodes();
	}

	/**
	 * Seta o atributo insideOf do node do grafo antivo, se ele foi contraido em algum node novo.
	 * @param oldGrafo
	 */
	private void setInsideOfForGraph(GraphWrapper oldGrafo) {
		try {
			oldGrafo.beginTransaction();
			Map<NodeWrapper, Long> insideOf = mathcing.getInsideOf();
			for (NodeWrapper node : oldGrafo.getAllNodes()) {
				Long insideId = insideOf.get(node);
				if (insideId != null) {
					node.setInsideOf(insideId);
					//TODO: também precisa fazer o esquema de commit do oldGraph.
				}
			}
			oldGrafo.endTransaction();
		} catch (Throwable e) {
			logger.error("Erro salvando insideOf.", e);
		} finally {
			oldGrafo.finish();
		}
	}

}
